//
//  AgoraSDKSettings.swift
//  Hermes
//
//  Created by FH on 2021/6/10.
//  Copyright © 2021 Agora. All rights reserved.
//

import Foundation

enum AgoraSDKSettingsUseMode: Int {
    case notUsed
    case usedByScript
    case usedByManual
}

class AppMediaIO : Codable {
    var bufferType = 1
    var pixelFormat: UInt = 1
}
extension AppMediaIO : Equatable, Serializable {
    static func == (lhs: AppMediaIO, rhs: AppMediaIO) -> Bool {
        return lhs.bufferType == rhs.bufferType && lhs.pixelFormat == rhs.pixelFormat
    }
    
    func encodeJson() -> String? {
        if let data = try? JSONEncoder().encode(self) {
            return String(data: data, encoding: .utf8)
        }
        return nil
    }
    
    func decodeJson(_ json: String) -> Any? {
        if let data = json.data(using: .utf8),
           let result = try? JSONDecoder().decode(AppMediaIO.self, from: data) {
            return result
        }
        return nil
    }
}

class AppAudioSource : Codable {
    var sampleRate = 44100
    var channelsPerFrame = 2
}
extension AppAudioSource : Equatable, Serializable {
    static func == (lhs: AppAudioSource, rhs: AppAudioSource) -> Bool {
        return lhs.sampleRate == rhs.sampleRate && lhs.channelsPerFrame == rhs.channelsPerFrame
    }
    
    func encodeJson() -> String? {
        if let data = try? JSONEncoder().encode(self) {
            return String(data: data, encoding: .utf8)
        }
        return nil
    }
    
    func decodeJson(_ json: String) -> Any? {
        if let data = json.data(using: .utf8),
           let result = try? JSONDecoder().decode(AppAudioSource.self, from: data) {
            return result
        }
        return nil
    }
}

final class AgoraSDKSettings : PropsStatesContext, Deinitable {
    var dealloc = DeallocObserver()
    var isBinding: Bool {
        return self.$videoDimension.isBinding
    }
    // general
    @PropState
    var validAudioTime: AppTokenValidTime = .infinity
    @PropState
    var validVideoTime: AppTokenValidTime = .infinity
    @PropState
    var validDataTime: AppTokenValidTime = .infinity
    @PropState
    var validJoinTime: AppTokenValidTime = .infinity
    @PropState
    var enableVideoSource = false
    @PropState
    var mediaIOVideoSource = AppMediaIO()
    @PropState
    var enableLocalRender = false
    @PropState
    var mediaIOLocalRender = AppMediaIO()
    @PropState
    var enableRemoteRender = false
    @PropState
    var mediaIORemoteRender = AppMediaIO()
    @PropState
    var enableAudioSource = false
    @PropState
    var externalAudioSource = AppAudioSource()
    // video
    @PropState
    var asVoiceSDK = false
    @PropState
    var shouldDisableVideo: Bool? = false
    @PropState
    var shouldDisableLocalPreview: Bool? = false
    @PropState
    var videoRenderMode: AgoraVideoRenderMode = .hidden
    @PropState
    var videoDimension: AppVideoDimension = .dim640x480
    @PropState
    var videoFrameRate: AppVideoFrameRate = .fps15
    @PropState
    var videoBitrate: AppVideoBitrate = .standard
    
    init(_ store: StatesStore, initState: [String : Any]=[:]) {
        self.bindStates(sectionName: "settings", statesStore: store, initStates: ("settings", initState))
    }
    
    init() { }
    
    static func encode(_ settings: [String : Any]) -> [String : String] {
        var result = [String : String]()
        for (key, value) in settings {
            if let obj = value as? Serializable {
                if let json = obj.encodeJson() {
                    result[key] = json
                }
            } else if let raw = value as? RawEnumConvertible {
                let (valueType, valueStr) = getAnyValueStr(from: raw.anyRawValue)
                result[key] = "\(valueType)^*^\(valueStr)"
            } else {
                let (valueType, valueStr) = getAnyValueStr(from: value)
                result[key] = "\(valueType)^*^\(valueStr)"
            }
        }
        return result
    }
    
    static func decode(_ encoder: [String : String], store: StatesStore) -> [String : Any] {
        var result = [String : Any]()
        for (key, value) in encoder {
            if value.contains("^*^") {
                let split = value.components(separatedBy: "^*^")
                if let state = getAnyValue(by: split[0], and: split[1]) {
                    result.updateValue(state, forKey: key)
                } else {
                    let state: Bool? = nil
                    result.updateValue(state as Any, forKey: key)
                }
            } else {
                if let state = store.getState(key, sectionName: "settings"),
                          let obj = state as? Serializable,
                          let decoder = obj.decodeJson(value) {
                    result[key] = decoder
                }
            }
        }
        return result
    }

    func detectUpdated(store: StatesStore,
                       _ editByScript:  [String : Any]?, _ editByManual:  [String : Any]?) -> (Bool, Bool) {
        let currDict = store.toDictionary(sectionName: "settings")
        var updatedByScript = false
        var updatedByManual = false
        if let dict = editByManual {
            if dict.count > currDict.count {
                updatedByManual = true
            } else if dict.count == currDict.count {
                updatedByManual = !Self.equals(lhs: dict, rhs: currDict)
            }
        }
        if let dict = editByScript {
            if dict.count > currDict.count {
                updatedByScript = true
            } else if dict.count == currDict.count {
                updatedByScript = !Self.equals(lhs: dict, rhs: currDict)
            }
            if updatedByManual && updatedByScript {
                updatedByManual = !Self.equals(lhs: dict, rhs: editByManual!)
                if updatedByManual {
                    updatedByScript = false
                }
            }
        }
        return (updatedByScript, updatedByManual)
    }
    
    static func equals(lhs: [String : Any], rhs: [String : Any]) -> Bool {
        for (key, value) in lhs {
            if let rhsValue = rhs[key] {
                if let complex = value as? ComplexCaseIterable,
                    let rhsComplex = rhsValue as? ComplexCaseIterable {
                    if complex.getCurrIndex() != rhsComplex.getCurrIndex() {
                        return false
                    }
                } else if let complex = value as? ComplexCaseIterable {
                    if !complex.equal(other: rhsValue) {
                        return false
                    }
                } else if let complex = rhsValue as? ComplexCaseIterable {
                    if !complex.equal(other: value) {
                        return false
                    }
                } else if value is Serializable && rhsValue is Serializable {
                    if let lhs = value as? AppMediaIO, let rhs = rhsValue as? AppMediaIO {
                        if lhs != rhs {
                            return false
                        }
                    } else if let lhs = value as? AppAudioSource, let rhs = rhsValue as? AppAudioSource {
                        if lhs != rhs {
                            return false
                        }
                    }
                } else {
                    if !equalsAny(lhs: value, rhs: rhsValue) {
                        return false
                    }
                }
            } else {
                return false
            }
        }
        return true
    }
}
